import { MipsConverter } from "./mips-converter";

export class PlantumlConverter {
  constructor(private pcode: [string, number, number][]) {
    this.convert();
  }
  public getHeight([op, a, b]: [string, number, number]) {
    switch (op) {
      case "opr":
        return [NaN, 0, -1, -1, -1, -1, 0, 0, -1, -1, -1, -1, -1, -1, -1, 0, 1][
          b
        ];
      case "int":
        return b;
      case "cal":
        return 0;
      case "lit":
        return 1;
      case "lod":
        return 1;
      case "sto":
        return -1;
      case "jmp":
        return 0;
      case "jpc":
        return -1;
      default:
        return 0;
    }
  }
  /** 基本块 */
  public blocks: [string, number, number][][] = [];
  /** 基本块之间的箭头 */
  public arrows: number[][];
  /** 栈高变化 */
  public delta: string[];
  /** plantuml结果 */
  public result: string[] = [];
  /** 行与blockid的映射 */
  public position: number[];
  private convert() {
    let block: [string, number, number][] = [];
    let jumpto: number[] = [];
    let breakpoints = [];
    this.pcode.forEach(([op, a, b]) => {
      if (op === "jmp" || op === "jpc" || op === "cal") {
        breakpoints.push(b);
      }
    });
    this.pcode.forEach(([op, a, b]: any, i: number) => {
      if (op === "jmp" || op === "jpc" || op === "cal") {
        block.push([op, a, b]);
        jumpto.push(b);
        this.blocks.push(block);
        block = [];
      } else if (op === "opr" && a === 0 && b === 0) {
        block.push([op, a, b]);
        jumpto.push(-1);
        this.blocks.push(block);
        block = [];
      } else if (breakpoints.includes(i + 1)) {
        block.push([op, a, b]);
        jumpto.push(i + 1);
        this.blocks.push(block);
        block = [];
      } else {
        block.push([op, a, b]);
      }
    });
    this.position = this.blocks
      .map((x) => x.length)
      .reduce((p, c) => p.concat([p[p.length - 1] + c]), [0]);
    // console.log(blocks.map(x => x.length));
    // console.log(jumpto)
    jumpto = jumpto.map((x) => (x >= 0 ? this.position.indexOf(x) : x));
    this.arrows = jumpto
      .map((v, i) => (v !== -1 ? [i, v] : null))
      .filter((x) => x !== null);
    this.arrows = this.arrows.concat(
      this.blocks
        .map((block, i) =>
          block[block.length - 1][0] === "jpc" ? [i, i + 1] : null
        )
        .filter((x) => x)
    );
    // 返回箭头
    let return_arrows = [];
    this.blocks.forEach((v, i) => {
      if (jumpto[i] === -1) {
        let source = this.arrows
          .filter(([a, b]) => b === i && a !== i)
          .map(([a, b]) => a);
        let record = new Set([i, ...source]);
        while (source.length) {
          const s = source.pop();
          const t = this.arrows
            .filter(([a, b]) => b === s && !record.has(a))
            .map(([a, b]) => a);
          t.forEach((x) => {
            record.add(x);
            const block = this.blocks[x];
            const instruction = block[block.length - 1];
            if (instruction[0] === "cal" && x + 1 < this.blocks.length) {
              return_arrows.push([i, x + 1]);
            } else {
              source.push(x);
            }
          });
        }
      }
    });
    this.arrows = this.arrows.concat(return_arrows);
    // 计算栈变化
    const deltaNumber = this.blocks.map((block) =>
      block.map((x) => this.getHeight(x)).reduce((p, c) => p + c, 0)
    );
    this.delta = deltaNumber.map((x) => x.toString());
    deltaNumber.forEach((v, i) => {
      if (isNaN(v)) {
        this.delta[i] =
          return_arrows
            .filter(([a, b]) => a === i)
            .map(([a, b]) => {
              const block = this.blocks[b - 1];
              const target = block[block.length - 1][2];
              // 栈区高度不变这个宣称过强了，说不定会有各种BUG
              return (-deltaNumber[this.position.indexOf(target)]).toString();
            })
            .join("|") + "?";
      }
    });
    // 这里就是强行推断了……
    this.delta[this.delta.length - 1] =
      (-deltaNumber[this.position.indexOf(this.blocks[0][0][2])]).toString() +
      "?";
    this.result = this.blocks_to_dot();
  }

  private blocks_to_dot(): string[] {
    let nodes = this.blocks.map(
      (block: [string, number, number][], i: number) =>
        `B${i} [label="B${i}(Δ=${this.delta[i]})\\n${block
          .map((x, j) => (this.position[i] + j).toString() + " " + x.join(" "))
          .join("\\n")}"]`
    );
    let edges = this.arrows.map(([a, b]) => `B${a} -> B${b}`);
    return `@startdot flowchart
digraph flowchart {
    rankdir=LR;
    node [shape = rectangle];
${nodes.map((x: string) => "    " + x).join("\n")}
${edges.map((x: string) => "    " + x).join("\n")}
}
@enddot`.split("\n");
  }

  optimizeds_convert() {
    let result = [].concat(
      ...this.blocks.map((x, i) =>
        [`lb${this.position[i]}:`].concat(this.optimizeds_block_convert(x))
      )
    );
    // 删除不使用的label
    const labels = {};
    result.forEach((v) => {
      const temp = /lb\d+(?!(:|\d))/.exec(v);
      if (temp) {
        temp.forEach((x) => (labels[x] = (labels[x] ? labels[x] : 0) + 1));
      }
    });
    result = result.filter(
      (x) => !x.startsWith("lb") || x.substr(0, x.length - 1) in labels
    );
    // 删除块内冗余的存入>取出指令
    for (let i = 0; i < result.length - 1; ++i) {
      if (result[i] === "sw $t0, ($sp)" && result[i + 1] === "lw $t0, ($sp)") {
        result[i + 1] = "";
        ++i;
      }
    }
    result = result.filter((x) => x);
    return [".text", "main:", "move $t2, $sp"].concat(result);
  }

  optimizeds_block_convert(block: [string, number, number][]) {
    const mipsConverter = new MipsConverter();
    // 复制
    block = <[string, number, number][]>[...block.map((x) => [...x])];
    const getBlock: (arg0: number) => [string, number, number] = (
      i: number
    ) => {
      if (i >= 0 && i < block.length) {
        return block[i];
      } else {
        return ["nop", 0, 0];
      }
    };
    const clearBlock = (i: number, length: number) => {
      for (let j = 0; j < length; ++j) {
        if (i + j >= 0 && i + j < block.length) {
          block[i + j] = ["nop", 0, 0];
        }
      }
    };
    const result = Array.from({ length: block.length }).map((x) => []);
    for (let i = 0; i < block.length; ++i) {
      const p1 = getBlock(i);
      const p2 = getBlock(i + 1);
      // 对变量直接赋值的少许优化
      if (p1[0] === "lod" && p2[0] === "sto") {
        clearBlock(i, 2);
        if (p1[1] <= p2[1]) {
          result[i] = ["move $t0, $t2"]
            .concat(Array.from({ length: p1[1] }).map((v) => "lw $t0, ($t0)"))
            .concat([`lw $t1, ${p1[2] * 4}($t0)`])
            .concat(
              Array.from({ length: p2[1] - p1[1] }).map((v) => "lw $t0, ($t0)")
            )
            .concat([`sw $t1, ${p2[2] * 4}($t0)`]);
        } else {
          result[i] = ["move $t0, $t2"]
            .concat(Array.from({ length: p1[1] }).map((v) => "lw $t0, ($t0)"))
            .concat(["move $t3, $t0"])
            .concat(
              Array.from({ length: p2[1] - p1[1] }).map((v) => "lw $t0, ($t0)")
            )
            .concat([`lw $t1, ${p1[2] * 4}($t0)`])
            .concat([`sw $t1, ${p2[2] * 4}($t3)`]);
        }
        ++i;
      }
      // 对变量直接赋常量的少许优化
      else if (p1[0] === "lit" && p2[0] === "sto") {
        clearBlock(i, 2);
        result[i] = ["move $t0, $t2", `li $t1, ${p1[2]}`]
          .concat(Array.from({ length: p2[1] }).map((v) => "lw $t0, ($t0)"))
          .concat([`sw $t1, ${p2[2] * 4}($t0)`]);
        ++i;
      }
      // 对连续运算的优化
      else if (p1[0] === "lod") {
        let lod = [p1];
        let j = 1;
        for (; i + j < block.length; ++j) {
          const temp = getBlock(i + j);
          if (temp[0] === "lod" || temp[0] === "lit") {
            lod.push(temp);
          } else {
            break;
          }
        }
        const opr = Array.from({ length: lod.length - 1 }).map((x, y) =>
          getBlock(i + j + y)
        );
        const sto = getBlock(i + j + j - 1);
        if (
          opr.every(
            (x) =>
              x[0] === "opr" &&
              ((x[2] >= 2 && x[2] <= 5) || (x[2] >= 8 && x[2] <= 13))
          ) &&
          sto[0] === "sto" &&
          j <= 6
        ) {
          clearBlock(i, j * 2);
          const maxLayer = Math.max(
            ...lod.filter((x) => x[0] === "lod").map((x) => x[1]),
            sto[1]
          );
          let instructions = ["move $t0, $t2"];
          for (let layer = 0; layer <= maxLayer; ++layer) {
            lod.forEach((x, y) => {
              if (x[0] === "lod" && x[1] === layer) {
                instructions.push(`lw $t${4 + y}, ${x[2] * 4}($t0)`);
              }
            });
            if (sto[1] === layer) {
              instructions.push(`move $t3, $t0`);
            }
            instructions.push("lw $t0, ($t0)");
          }
          instructions.pop();
          lod.forEach((x, y) => {
            if (x[0] === "lit") {
              instructions.push(`li $t${4 + y}, ${x[2]}`);
            }
          });
          if (opr.length) {
            instructions.push(`move $t0, $t${lod.length - 1 + 4}`);
            for (let index = 0; index < opr.length; ++index) {
              let temp = mipsConverter.toMips(...opr[index]);
              temp = [...temp.slice(3, temp.length - 1)];
              instructions = instructions.concat(
                ...temp.map((x) =>
                  x.split("$t1").join(`$t${lod.length - index - 2 + 4}`)
                )
              );
            }
          }
          // store
          instructions.push(`sw $t0, ${sto[2] * 4}($t3)`);
          result[i] = instructions;
          i += j * 2 - 1;
        }
      }
    }
    block.forEach((v, i) => {
      if (v[0] !== "nop") {
        result[i] = mipsConverter.toMips(...v);
      }
    });
    return [].concat(...result);
  }
}
